#include <stdio.h>
#include <CL/cl.h>
#include "aux.h"

int main(void)
{
    // Platform Creation
    cl_platform_id platform;
    clGetPlatformIDs(1, &platform, NULL);
    printf("...First platform chosen\n");

    // Device Creation
    cl_device_id device;
    clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 1, &device, NULL);
    printf("...First device chosen\n");

    // Context Creation
    cl_context context;
    cl_int err;
    context = clCreateContext(NULL, 1, &device, NULL, NULL, &err);
    if (err==CL_SUCCESS){
        printf("Context created\n");
    } else {
        printf("Error creating context\n");
    }

    // Program Objects:
    const char* programSource = readKernel();
    cl_program program;
    program = clCreateProgramWithSource(context, 1,
                                        (const char**)&programSource,
                                        NULL, &err);
    if (err==CL_SUCCESS){
        printf("Program created\n");
    } else {
        printf("Error creating program\n");
    }

    err = clBuildProgram(program, 1, &device, NULL, NULL, NULL);
    if (err==CL_SUCCESS){
        printf("Program compiled\n");
    } else {
        printf("Error compiling program\n");
    }

    cl_kernel kernel;
    kernel = clCreateKernel(program, "matrix_mul", &err);
    if (err==CL_SUCCESS){
        printf("Kernel created\n");
    } else {
        printf("Error creating kernel\n");
    }

    // Host Data
    float a_data[A_HEIGHT][A_WIDTH];
    float b_data[B_HEIGHT][B_WIDTH];
    float c_res[A_HEIGHT][B_WIDTH];
    printf("----------------------------------\n");
    for (int i=0; i<A_HEIGHT; i++){
        for (int j=0; j<A_WIDTH; j++){
            a_data[i][j] = (100.0*rand())/RAND_MAX;
            printf("A%d%d: %f  ", i, j, a_data[i][j]);
        }
        printf("\n");
    }
    printf("----------------------------------\n");
    for (int i=0; i<B_HEIGHT; i++){
        for (int j=0; j<B_WIDTH; j++){
            b_data[i][j] = (100.0*rand())/RAND_MAX;
            printf("B%d%d: %f  ", i, j, b_data[i][j]);
        }
        printf("\n");
    }
    printf("----------------------------------\n");

    // Memory Objects:
    cl_mem a_in, b_in, c_out;

    a_in = clCreateBuffer(context, CL_MEM_READ_ONLY,
                          sizeof(float)*A_HEIGHT*A_WIDTH, NULL, &err);
    if (err==CL_SUCCESS){
        printf("Buffer A created\n");
    } else {
        printf("Error creating buffer A\n");
    }

    b_in = clCreateBuffer(context, CL_MEM_READ_ONLY,
                          sizeof(float)*B_HEIGHT*B_WIDTH, NULL, &err);
    if (err==CL_SUCCESS){
        printf("Buffer B created\n");
    } else {
        printf("Error creating buffer B\n");
    }

    c_out = clCreateBuffer(context, CL_MEM_WRITE_ONLY,
                           sizeof(float)*A_HEIGHT*B_WIDTH, NULL, &err);
    if (err==CL_SUCCESS){
        printf("Buffer C created\n");
    } else {
        printf("Error creating buffer C\n");
    }

    // Set Kernel Arguments
    cl_int aw = A_WIDTH;
    cl_int ah = A_HEIGHT;
    cl_int bw = B_WIDTH;
    cl_int bh = A_HEIGHT;
    err = clSetKernelArg(kernel, 0, sizeof(cl_mem), &a_in);
    err |= clSetKernelArg(kernel, 1, sizeof(cl_mem), &b_in);
    err |= clSetKernelArg(kernel, 2, sizeof(cl_mem), &c_out);
    err |= clSetKernelArg(kernel, 3, sizeof(cl_int), &aw);
    err |= clSetKernelArg(kernel, 4, sizeof(cl_int), &ah);
    err |= clSetKernelArg(kernel, 5, sizeof(cl_int), &bw);
    err |= clSetKernelArg(kernel, 6, sizeof(cl_int), &bh);
    if (err==CL_SUCCESS){
        printf("Kernel's arguments configured\n");
    } else {
        printf("Error configuring the kernel's arguments\n");
    }

    // Create Command Queue
    cl_command_queue commands;
    commands = clCreateCommandQueue(context, device, 0, &err);
    if (err==CL_SUCCESS){
        printf("Command queue created\n");
    } else {
        printf("Error creating command queue\n");
    }

//     cl_event writeA;
//     cl_event writeB;
//     cl_event kernelExec;
//     cl_event readC;
//     err = clEnqueue...();
//     cl_event eventlist[2] = {writeA, writeB}

    // Memory Write
    err = clEnqueueWriteBuffer(commands, a_in, CL_TRUE, 0,
                               sizeof(float)*A_HEIGHT*A_WIDTH,
                               a_data, 0, NULL, NULL);
    if (err==CL_SUCCESS){
        printf("Buffer A copied to device\n");
    } else {
        printf("Error copying buffer A to device\n");
    }

    err = clEnqueueWriteBuffer(commands, b_in, CL_TRUE, 0,
                               sizeof(float)*B_HEIGHT*B_WIDTH,
                               b_data, 0, NULL, NULL);
    if (err==CL_SUCCESS){
        printf("Buffer B copied to device\n");
    } else {
        printf("Error copying buffer B to device\n");
    }

    // Kernel Execution
    // Set local and global workgroup sizes
    // We assume the result matrix dimensions are divisible by global[0], global[1]
    size_t global[2] = {B_WIDTH, A_HEIGHT};
    size_t local[2] = {A_HEIGHT, B_WIDTH};

    err = clEnqueueNDRangeKernel(commands, kernel, 2, NULL, global, local, 0,
                                 NULL, NULL);
    if (err==CL_SUCCESS){
        printf("Kernel sent to device\n");
    } else {
        printf("Error sending kernel to device\n");
        printf("%d\n", err);
    }

    clFinish(commands);

    // Memory Read
    err = clEnqueueReadBuffer(commands, c_out, CL_TRUE, 0,
                              sizeof(float)*A_HEIGHT*B_WIDTH,
                              c_res, 0, NULL, NULL);
    if (err==CL_SUCCESS){
        printf("Buffer C copied from device\n");
    } else {
        printf("Error copying buffer C from device\n");
    }

    printf("----------------------------------\n");
    for (int i=0; i<A_HEIGHT; i++){
        for (int j=0; j<B_WIDTH; j++){
            printf("%f  ", c_res[i][j]);
        }
        printf("\n");
    }
    printf("----------------------------------\n");

    return 0;
}
